--- file_format: mystnb kernelspec: name: python3 --- (sec-tutorial-sched-pulse)= # Tutorial: Schedules and Pulses ```{seealso} The complete source code of this tutorial can be found in {nb-download}`Schedules and Pulses.ipynb` ``` ## The Schedule The main data structure that describes an experiment in the `quantify-scheduler` is the Schedule. We will show how the Schedule works through an example. ```{code-cell} ipython3 from quantify_scheduler import Schedule sched = Schedule("Hello quantum world!") sched ``` As we can see, our newly created schedule is still empty. We need to manually add operations to it. In `quantify-scheduler` there are three types of operations: pulses, acquisitions and gates. All of these have explicit timing control. In this tutorial, we will only cover pulses. The goal will not be to make a schedule that is physically meaningful, but to demonstrate the control over the scheduling to its fullest. While it is possible to define a pulse completely from scratch, we will be using some of the pulse definitions provided with the `quantify-scheduler`. These pulses are described in the {mod}`quantify_scheduler.operations` submodule. It's worth noting that no sampling of the data yet occurs at this stage, but the pulse is kept in a parameterized form. We will add a square pulse from the operation library to the schedule. ```{code-cell} ipython3 from quantify_scheduler.operations import SquarePulse square_pulse = sched.add( SquarePulse(amp=1, duration=1e-6, port="q0:res", clock="q0.ro") ) sched ``` You may have noticed that we passed a {code}`port` and a {code}`clock` to the pulse. The {code}`port` specifies the physical location on the quantum chip to which we are sending the pulses, whilst the {code}`clock` tracks the frequency of the signal (see {ref}`sec-user-guide-ports-clocks`). This clock frequency has not yet been defined, so prior to any compilation step this clock needs to be added to the schedule as a resource. ```{code-cell} ipython3 from quantify_scheduler import ClockResource readout_clock = ClockResource(name="q0.ro", freq=7e9) sched.add_resource(readout_clock) sched ``` We now perform the compilation of the schedule onto the {ref}`sec-user-guide-quantum-device`. This step is necessary to, among other things, determine the absolute timing of the pulses. The compilation step is described in more detail in {ref}`sec-compilation`. ```{code-cell} ipython3 from quantify_scheduler import QuantumDevice, SerialCompiler quantum_device = QuantumDevice("quantum_device") device_compiler = SerialCompiler("Device compiler", quantum_device) comp_sched = device_compiler.compile(sched) ``` `quantify-scheduler` provides several visualization tools to show a visual representation of the schedule we made. In the cell below, we draw the schedule using a {meth}`pulse diagram <.plot_pulse_diagram>`. Note that these plots are interactive and modulation is not shown by default. ```{code-cell} ipython3 comp_sched.plot_pulse_diagram(plot_backend="plotly") ``` ## Explicit timing control What we see in the pulse diagram is only a flat line, corresponding to our single square pulse. To make our schedule more interesting, we should add more pulses to it. We will add another square pulse, but with a 500 ns delay. ```{code-cell} ipython3 sched.add( SquarePulse(amp=1, duration=1e-6, port="q0:res", clock="q0.ro"), ref_op=square_pulse, rel_time=500e-9, ) comp_sched = device_compiler.compile(sched) comp_sched.plot_pulse_diagram(plot_backend="plotly") ``` We can see that {code}`rel_time=500e-9` schedules the pulse 500 ns shifted relative to the end of the {code}`ref_op`. If no additional arguments are passed, operations are added directly after the operation that was added last. Let's now instead align a pulse to start at the same time as the first square pulse. Before, we specified the timing relative to the end of a different pulse, but we can choose to instead specify it relative to the beginning. This is done by passing {code}`ref_pt="start"`. ```{code-cell} ipython3 from quantify_scheduler.operations import DRAGPulse sched.add( DRAGPulse( G_amp=0.5, D_amp=0.5, duration=1e-6, phase=0, port="q0:mw", clock="q0.01" ), ref_op=square_pulse, ref_pt="start", ) sched.add_resource(ClockResource(name="q0.01", freq=7e9)) comp_sched = device_compiler.compile(sched) comp_sched.plot_pulse_diagram(plot_backend="plotly") ``` We see that we added a DRAG pulse to the schedule. Two things stand out: 1. The DRAG pulse is plotted separately from the square pulse, this is because we specified a different {code}`port` for this pulse than we did for the square pulse. 2. The DRAG pulse shows two lines instead of one. This is because a DRAG pulse is specified as a complex-valued pulse, so we have to plot both the I and Q components of the signal. The real part of the waveform is shown in color, whereas the imaginary component is shown in grayscale. ## Parameterized schedules In an experiment, often the need arises to vary one of the parameters of a schedule programmatically. Currently, the canonical way of achieving this is by defining a function that returns a generated schedule. We will use this to generate a pulse train, where we can specify the timing parameters separately. ```{code-cell} ipython3 from quantify_scheduler import BasebandClockResource def pulse_train_schedule( amp: float, time_high: float, time_low: float, amount_of_pulses: int ) -> Schedule: sched = Schedule("Pulse train schedule") square_pulse = sched.add( SquarePulse( amp=amp, duration=time_high, port="q0:fl", clock=BasebandClockResource.IDENTITY, ), ) for _ in range(amount_of_pulses - 1): square_pulse = sched.add( SquarePulse( amp=amp, duration=time_high, port="q0:fl", clock=BasebandClockResource.IDENTITY, ), rel_time=time_low, ref_op=square_pulse, ) return sched sched = pulse_train_schedule(1, 200e-9, 300e-9, 5) comp_sched = device_compiler.compile(sched) comp_sched.plot_pulse_diagram(plot_backend="plotly") ``` Note that we used the {class}`~quantify_scheduler.resources.BasebandClockResource` as a clock, which is always at 0 Hz and was added automatically to the schedule for convenience. We can see that the pulses start every 500 ns and are 200 ns long.